package invertedindex;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

public class Search {

	public Map<Integer, Double> query(String keywords) {
		String keys[] = keywords.trim().split(" ");
		Map<Integer, int[]>[] maps = new HashMap[keys.length];
		Map<Integer, Double> result = new HashMap<Integer, Double>();
		int totalNumberOfDoc = getTotalDoc();

		// get maps for each search key
		for (int i = 0; i < keys.length; i++) {
			maps[i] = getResultMap(keys[i]);
		}

		// merge the result maps into one map
		Map<Integer, int[]> firstMap = maps[0];
		if(firstMap == null)
			return new HashMap<Integer, Double>();
		Iterator<Integer> i = firstMap.keySet().iterator();
		while (i.hasNext()) {
			int docID = i.next();
			boolean merge = true;

			// determine whether to merge the current key value pair with the
			// final result map
			for (int j = 1; j < keys.length; j++) {
				Map<Integer, int[]> map = maps[j];
				if(map == null)
					continue;
				if (map.get(docID) == null) {
					merge = false;
					break;
				}
			}

			if (merge) {
				double rank = 0.0;
				for (int j = 0; j < keys.length; j++) {
					Map<Integer, int[]> map = maps[j];
					if(map == null)
						continue;
					int number_docs_appear = map.size();
					// System.out.println(docID);
					rank = rank
							+ computeTF_IDF(map.get(docID)[0],
									map.get(docID)[1], totalNumberOfDoc,
									number_docs_appear);

				}
				result.put(docID, rank);
			}
		}
		return sortMap(result);
	}

	private Map<Integer, Double> sortMap(Map<Integer, Double> unsorted) {
		Map<Integer, Double> sorted = new LinkedHashMap<Integer, Double>();

		// Store all the keys and values in the map to 2 lists.
		List<Integer> mapKeys = new ArrayList<Integer>(unsorted.keySet());
		List<Double> mapValues = new ArrayList<Double>(unsorted.values());

		// Store values in a sorted tree set
		List<Double> sortedSet = new ArrayList<Double>(mapValues);

		Collections.sort(sortedSet, new Comparator<Double>() {
			public int compare(Double d1, Double d2) {
				int compare = (int) (d1 * 1000 - d2 * 1000);
				return compare;
			}
		});

		// Convert the sorted tree set to an array
		Object[] sortedArray = sortedSet.toArray();

		// Iterate through the sorted array by descending order, and store the
		// key and value to the new sorted map
		int size = sortedArray.length;
		for (int i = size - 1; i >= 0; i--) {
			int docID = mapKeys.get(mapValues.indexOf(sortedArray[i]));
			Double rank = (Double) sortedArray[i];

			// clear the value in mapValues, so if there is a duplicate value,
			// it will get the next docid.
			mapKeys.set(mapValues.indexOf(sortedArray[i]), -1);
			mapValues.set(mapValues.indexOf(sortedArray[i]), -0.0);

			sorted.put(docID, rank);
		}

		return sorted;
	}

	// Query DB to get the total number of doc
	private int getTotalDoc() {
		int count = -1;
		try {
			DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());

			Connection conn = DriverManager.getConnection(
					"jdbc:oracle:thin:@localhost:1521:TEACH", "wunan",
					"uviccsc");

			PreparedStatement stmt = conn
					.prepareStatement("SELECT count (docid) as count from documents");

			ResultSet rset = stmt.executeQuery();

			if (rset.next()) 
				count =  rset.getInt("count");
			conn.close();
			
			return count;
		} catch (SQLException e) {
			System.out.println(e);
			return count;
		}
	}
	
	// Query DB to get the max frequency of the word
	private Map<Integer, int[]> getMaxFreq(Map<Integer, Integer> resultInInt) {
		// The result map has a int array and length of 2 as the value. the
		// first entry in the array is the count number in the doc, the second
		// entry in the array is the max frequency
		Map<Integer, int[]> result = new HashMap<Integer, int[]>();
		try {
			DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());

			Connection conn = DriverManager.getConnection(
					"jdbc:oracle:thin:@localhost:1521:TEACH", "wunan",
					"uviccsc");

			Statement stmt = conn.createStatement();
			String query = "SELECT docid, maxfreq from documents WHERE docid = ";

			Iterator<Integer> i = resultInInt.keySet().iterator();
			while (i.hasNext()) {
				query = query + i.next();
				if (i.hasNext())
					query = query
							+ " union SELECT docid, maxfreq from documents WHERE docid = ";
			}
			
			ResultSet rset = stmt.executeQuery(query);

			while (rset.next()) {
				int key = rset.getInt("docid");
				int[] value = new int[2];
				value[0] = resultInInt.get(key);
				value[1] = rset.getInt("maxfreq");
				result.put(key, value);
			}
			conn.close();

			return result;
		} catch (SQLException e) {
			System.out.println(e);
			return null;
		}
	}

	// Compute the TF-IDF value
	private double computeTF_IDF(int count, int maxFreq, int totalNumberOfDoc,
			int number_docs_appear) {
		double tf;
		double idf;

		tf = (double) count / maxFreq;
		idf = Math.log((double) (totalNumberOfDoc / number_docs_appear));
		// System.out.println(count + " " + maxFreq + " " + tf);
		return tf * idf;
	}

	// Query DB to get the records which contagetResultMapin the search key
	private Map<Integer, int[]> getResultMap(String keyword) {
		SortedMap<Integer, String> resultInHex = new TreeMap<Integer, String>();
		try {
			DriverManager.registerDriver(new oracle.jdbc.driver.OracleDriver());

			Connection conn = DriverManager.getConnection(
					"jdbc:oracle:thin:@localhost:1521:TEACH", "wunan",
					"uviccsc");

			PreparedStatement stmt = conn
					.prepareStatement("SELECT firstdoc, block from postings WHERE word=?");

			stmt.setString(1, keyword);

			ResultSet rset = stmt.executeQuery();

			while (rset.next()) {
				resultInHex.put(rset.getInt("firstdoc"), rset
						.getString("block"));
			}
			conn.close();

			SortedMap<Integer, Integer> resultInInt = constructResult(resultInHex);
			return getMaxFreq(resultInInt);
		} catch (SQLException e) {
			System.out.println(e);
			return null;
		}

	}

	// Decode the hex to string
	private SortedMap<Integer, Integer> constructResult(
			SortedMap<Integer, String> resultInHex) {

		SortedMap<Integer, Integer> resultList = Encoding
				.Strings2Map(resultInHex);

		return resultList;
	}

	public static void main(String[] args) {
		Search search = new Search();
		Map<Integer, Double> result = search.query("aaa");

		System.out.println("Total number of result is " + result.size());
		Iterator<Integer> i = result.keySet().iterator();
		while (i.hasNext()) {
			int docID = i.next();
			double rank = result.get(docID);
			System.out.println(docID + " " + rank);
		}
	}
}
